﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition;
using System.Reflection;
using System.ComponentModel.Composition.Primitives;
using Caliburn.Micro;

namespace NthDownload.Composition
{
    public static class ImportService
    {
        #region Constructor

        static ImportService()
        {
            //
            // Initialize the import service
            //
            Initialize();
        }

        #endregion Constructor

        #region Private Properties

        /// <summary>
        /// The primary part catalogue
        /// </summary>
        private static AggregateCatalog MainCatalogue { get; set; }

        /// <summary>
        /// The container used for part composition
        /// </summary>
        private static CompositionContainer Container { get; set; }

        #endregion Private Properties

        #region Private Methods

        /// <summary>
        /// Responsible for the initial initialization of the service
        /// </summary>
        private static void Initialize()
        {
            //
            // Instantiate catalogoue, container and engine 
            //
            MainCatalogue = new AggregateCatalog();
            Container = new CompositionContainer(MainCatalogue);

            //
            // Add the first assembly to be executed in the app domain to the main catalogue, this adds all exports it contains to the container
            //
            MainCatalogue.Catalogs.Add(new AssemblyCatalog(Assembly.GetEntryAssembly()));
        }

        /// <summary>
        /// Registers the assemblies contained in the catalogue with caliburn micro's assembly source.
        /// This allows them to be searched by caliburn micro when it's creating views for view models.
        /// </summary>
        /// <param name="catalogue">The catalogue which contains the assemblies to register</param>
        private static void RegisterPartAssemblies(ComposablePartCatalog catalogue)
        {
            if (catalogue == null) return;

            foreach (ComposablePartDefinition part in catalogue.Parts)
            {
                foreach (ExportDefinition definition in part.ExportDefinitions)
                {
                    foreach (object value in Container.GetExportedValues<object>(definition.ContractName))
                    {
                        Assembly assembly = value.GetType().Assembly;
                        if (AssemblySource.Instance.FirstOrDefault(a => a.FullName == assembly.FullName) == null)
                        {
                            AssemblySource.Instance.Add(assembly);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Deregisters the assemblies contained in the catalogue from caliburn micro's assembly source.
        /// </summary>
        /// <param name="catalogue">The catalogues which contains the parts to deregister</param>
        private static void DeregisterPartAssemblies(ComposablePartCatalog catalogue)
        {
            if (catalogue == null) return;

            foreach (ComposablePartDefinition part in catalogue.Parts)
            {
                foreach (ExportDefinition definition in part.ExportDefinitions)
                {
                    foreach (object value in Container.GetExportedValues<object>(definition.ContractName))
                    {
                        Assembly assembly = value.GetType().Assembly;
                        Assembly assemblyToRemove = AssemblySource.Instance.FirstOrDefault(a => a.FullName == assembly.FullName);
                        if (assemblyToRemove != null)
                        {
                            AssemblySource.Instance.Remove(assemblyToRemove);
                        }
                    }
                }
            }
        }

        #endregion Private Methods

        #region Methods

        /// <summary>
        /// Gets export values from the composition container
        /// </summary>
        /// <typeparam name="T">The type being retrieved.</typeparam>
        public static IEnumerable<T> GetExportValues<T>()
        {
            return Container.GetExportedValues<T>();
        }

        /// <summary>
        /// Gets export values from the composition container
        /// </summary>
        /// <typeparam name="T">The type being retrieved.</typeparam>
        /// <param name="contractName">The export contract.</param>
        public static IEnumerable<T> GetExportValues<T>(string contractName)
        {
            return Container.GetExportedValues<T>(contractName);
        }

        /// <summary>
        /// Gets an export value from the composition container
        /// </summary>
        /// <typeparam name="T">The type being retrieved.</typeparam>
        public static T GetExportValue<T>()
        {
            return Container.GetExportedValue<T>();
        }

        /// <summary>
        /// Gets an export value from the composition container
        /// </summary>
        /// <typeparam name="T">The type being retrieved.</typeparam>
        /// <param name="contractName">The export contract.</param>
        public static T GetExportValue<T>(string contractName)
        {
            return Container.GetExportedValue<T>(contractName);
        }

        /// <summary>
        /// Tries to get an export value from the composition container
        /// </summary>
        /// <typeparam name="T">The type being retrieved.</typeparam>
        public static bool TryGetExportValue<T>(out T value)
        {
            bool returnVal = true;

            try
            {
                value = GetExportValue<T>();
            }
            catch
            {
                value = default(T);
                returnVal = false;
            }

            return returnVal;
        }

        /// <summary>
        /// Tries to get an export value from the composition container
        /// </summary>
        /// <typeparam name="T">The type being retrieved.</typeparam>
        /// <param name="contractName">The export contract.</param>
        /// <param name="value">The exported value.</param>
        public static bool TryGetExportValue<T>(string contractName, out T value)
        {
            bool returnVal = true;

            try
            {
                value = GetExportValue<T>(contractName);
            }
            catch
            {
                value = default(T);
                returnVal = false;
            }

            return returnVal;
        }

        /// <summary>
        /// Adds a value to the composition container
        /// </summary>
        /// <typeparam name="T">The type of the value being added.</typeparam>
        /// <param name="value">The value to add.</param>
        public static void AddExportedValueToContainer<T>(T value)
        {
            CompositionBatch batch = new CompositionBatch();
            batch.AddExportedValue(value);
            Container.Compose(batch);
        }

        /// <summary>
        /// Adds a value to the composition container
        /// </summary>
        /// <typeparam name="T">The type of the value being added.</typeparam>
        /// <param name="contractName">The contract to register the value under.</param>
        /// <param name="value">The value to add.</param>
        public static void AddExportedValueToContainer<T>(string contractName, T value)
        {
            CompositionBatch batch = new CompositionBatch();
            batch.AddExportedValue(contractName, value);
            Container.Compose(batch);
        }

        /// <summary>
        /// Adds a catalogue to the composition container
        /// </summary>
        /// <param name="catalogue">The catalogue to add</param>
        public static void AddCatalogueToContainer(ComposablePartCatalog catalogue)
        {
            MainCatalogue.Catalogs.Add(catalogue);
            RegisterPartAssemblies(catalogue);
        }

        /// <summary>
        /// Removes a catalogue from the composition container
        /// </summary>
        /// <param name="catalogue">The catalogue to remove</param>
        public static void RemoveCatalogueFromContainer(ComposablePartCatalog catalogue)
        {
            MainCatalogue.Catalogs.Remove(catalogue);
            DeregisterPartAssemblies(catalogue);
        }

        /// <summary>
        /// Satisfies the imports on an object
        /// </summary>
        /// <param name="attributedPart">The part who's imports to satisfy.</param>
        public static void SatisfyImports(object attributedPart)
        {
            if (attributedPart == null)
            {
                throw new ArgumentNullException("attributedPart");
            }

            ComposablePart part = AttributedModelServices.CreatePart(attributedPart);
            CompositionBatch batch = new CompositionBatch();
            batch.AddPart(part);
            Container.Compose(batch);
        }

        /// <summary>
        /// Tries to satisfy the imports on an object
        /// </summary>
        /// <param name="attributedPart">The part who's imports to satisfy.</param>
        public static bool TrySatisfyImports(object attributedPart)
        {
            try
            {
                SatisfyImports(attributedPart);
            }
            catch
            {
                return false;
            }
            return true;
        }

        /// <summary>
        /// Satisfies the imports on an object
        /// </summary>
        /// <param name="part">The part who's imports to satisfy.</param>
        public static void SatisfyImports(ComposablePart part)
        {
            CompositionBatch batch = new CompositionBatch();
            batch.AddPart(part);
            Container.Compose(batch);
        }

        /// <summary>
        /// Tries to satisfy the imports on an object
        /// </summary>
        /// <param name="part">The part who's imports to satisfy.</param>
        public static bool TrySatisfyImports(ComposablePart part)
        {
            try
            {
                SatisfyImports(part);
            }
            catch
            {
                return false;
            }

            return true;
        }

        #endregion Methods
    }
}
